/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.netbeans.editor.ext; import org.netbeans.editor.*; import javax.swing.*; import javax.swing.Timer; import javax.swing.text.BadLocationException; import javax.swing.text.JTextComponent; import java.awt.*; import java.awt.event.*; import java.util.*; import java.util.List; /** * Support for displaying find and replace dialogs * * @author Miloslav Metelka * @version 1.00 */ public class FindDialogSupport extends WindowAdapter implements ActionListener { private static final String HISTORY_KEY_BASE = "FindDialogSupport."; //NOI18N /** The EditorSettings key storing the last location of the dialog. */ private static final String BOUNDS_FIND = HISTORY_KEY_BASE + "bounds-find"; // NOI18N private static final String BOUNDS_REPLACE = HISTORY_KEY_BASE + "bounds-replace"; // NOI18N /** This lock is used to create a barrier between showing/hiding/changing * the dialog and testing if the dialog is already shown. * it is used to make test-and-change / test-and-display actions atomic. * It covers the following four fields: findDialog, isReplaceDialog, * findPanel, findButtons */ private Object dialogLock = new Object(); /** Whether the currently visible dialog is for replace */ private boolean isReplaceDialog; /** The buttons used in the visible dialog */ private JButton findButtons[]; /** The FindPanel used inside the visible dialog */ private FindPanel findPanel; /** Currently visible dialog */ private Dialog findDialog; protected Timer incSearchTimer; private static final String MNEMONIC_SUFFIX = "-mnemonic"; // NOI18N private static final String A11Y_PREFIX = "ACSD_"; // NOI18N public FindDialogSupport() { int delay = SettingsUtil.getInteger(null, SettingsNames.FIND_INC_SEARCH_DELAY, 200); incSearchTimer = new Timer(delay, new WeakTimerListener(this)); incSearchTimer.setRepeats(false); } private JButton[] createFindButtons() { JButton[] buttons = new JButton[] { new JButton(LocaleSupport.getString("find-button-find")), // NOI18N new JButton(LocaleSupport.getString("find-button-replace")), // NOI18N new JButton(LocaleSupport.getString("find-button-replace-all")), // NOI18N new JButton(LocaleSupport.getString("find-button-cancel")) // NOI18N }; /* buttons[0].setMnemonic( LocaleSupport.getChar( "find-button-find" + MNEMONIC_SUFFIX, 'F' ) ); // NOI18N */ buttons[1].setMnemonic( LocaleSupport.getChar( "find-button-replace" + MNEMONIC_SUFFIX, 'R') ); // NOI18N buttons[2].setMnemonic( LocaleSupport.getChar( "find-button-replace-all" + MNEMONIC_SUFFIX, 'A' ) ); // NOI18N buttons[0].getAccessibleContext().setAccessibleDescription(LocaleSupport.getString(A11Y_PREFIX + "find-button-find")); // NOI18N buttons[1].getAccessibleContext().setAccessibleDescription(LocaleSupport.getString(A11Y_PREFIX + "find-button-replace")); // NOI18N buttons[2].getAccessibleContext().setAccessibleDescription(LocaleSupport.getString(A11Y_PREFIX + "find-button-replace-all")); // NOI18N buttons[3].getAccessibleContext().setAccessibleDescription(LocaleSupport.getString(A11Y_PREFIX + "find-button-cancel")); // NOI18N return buttons; } private void loadBounds( Dialog d, String key ) { d.pack(); // Position the dialog according to the history Rectangle lastBounds = (Rectangle)EditorState.get( key ); if( lastBounds != null ) { d.setBounds( lastBounds ); } else { // no history, center it on the screen Dimension dim = d.getPreferredSize(); Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); int x = Math.max( 0, (screen.width - dim.width)/2 ); int y = Math.max( 0, (screen.height - dim.height)/2 ); d.setLocation( x, y ); } } private void saveBounds( Dialog d, String key ) { EditorState.put( key, d.getBounds() ); } private Dialog createFindDialog(JPanel findPanel, final JButton[] buttons, final ActionListener l) { Dialog d = DialogSupport.createDialog( isReplaceDialog ? LocaleSupport.getString ("replace-title") : LocaleSupport.getString ("find-title" ), // NOI18N findPanel, false, // non-modal buttons, true, // sidebuttons, 0, // defaultIndex = 0 => findButton 3, // cancelIndex = 3 => cancelButton l //listener ); return d; } private void showFindDialogImpl( boolean isReplace ) { synchronized( dialogLock ) { if( findDialog != null ) { // we have a dialog, change or raise if( isReplaceDialog == isReplace ) { // raise only findDialog.toFront(); } else { // change (and raise ?) saveBounds( findDialog, isReplaceDialog ? BOUNDS_REPLACE : BOUNDS_FIND ); isReplaceDialog = isReplace; findButtons[1].setVisible( isReplace ); findButtons[2].setVisible( isReplace ); if( isReplace ) { findDialog.setTitle (LocaleSupport.getString ("replace-title")); // NOI18N findPanel.updateReplace(); } else { findDialog.setTitle (LocaleSupport.getString ("find-title")); // NOI18N findPanel.updateFind(); } loadBounds( findDialog, isReplace ? BOUNDS_REPLACE : BOUNDS_FIND ); } } else { // create and show new dialog of reqiured type. isReplaceDialog = isReplace; findButtons = createFindButtons(); findButtons[1].setVisible( isReplace ); findButtons[2].setVisible( isReplace ); findPanel = new FindPanel(); if( isReplace ) { findPanel.updateReplace(); } else { findPanel.updateFind(); } findDialog = createFindDialog( findPanel, findButtons, this ); loadBounds( findDialog, isReplace ? BOUNDS_REPLACE : BOUNDS_FIND ); findDialog.addWindowListener( this ); } } // end of synchronized section findDialog.setVisible(true); findDialog.requestFocus(); findPanel.updateFocus(); } public void windowActivated(WindowEvent evt) { incSearchTimer.start(); } public void windowDeactivated(WindowEvent evt) { incSearchTimer.stop(); FindSupport.getFindSupport().incSearchReset(); } public void windowClosing(WindowEvent evt) { hideDialog(); } public void windowClosed(WindowEvent evt) { incSearchTimer.stop(); FindSupport.getFindSupport().incSearchReset(); Utilities.returnFocus(); } public void showFindDialog() { showFindDialogImpl(false); } public void showReplaceDialog() { showFindDialogImpl(true); } private void hideDialog() { synchronized( dialogLock ) { if( findDialog != null ) { saveBounds( findDialog, isReplaceDialog ? BOUNDS_REPLACE : BOUNDS_FIND); findDialog.dispose(); findButtons = null; // let it gc() findPanel = null; findDialog = null; } } } public void actionPerformed(ActionEvent evt) { JButton[] fb = findButtons; if( fb == null ) return; Object src = evt.getSource(); FindSupport fSup = FindSupport.getFindSupport(); if (src == fb[0]) { // Find button findPanel.updateFindHistory(); findPanel.save(); if (fSup.find(null, false)) { // found } if (!isReplaceDialog) { hideDialog(); } } else if (src == fb[1]) { // Replace button findPanel.updateFindHistory(); findPanel.updateReplaceHistory(); findPanel.save(); try { if (fSup.replace(null, false)) { // replaced fSup.find(null, false); } } catch (GuardedException e) { // replace in guarded block } catch (BadLocationException e) { e.printStackTrace(); } } else if (src == fb[2]) { // Replace All button findPanel.updateFindHistory(); findPanel.updateReplaceHistory(); findPanel.save(); fSup.replaceAll(null); } else if (src == fb[3]) { // Cancel button hideDialog(); // fix for issue 13502 // canceling dialog must scroll back to the caret position in the document // (in case the visible area of document has changed because of incremental search) JTextComponent c = Utilities.getLastActiveComponent(); if (c != null) c.getCaret().setDot(c.getCaret().getDot()); } else if (src == incSearchTimer) { fSup.incSearch(findPanel.getFindProps()); } else { // fix for issue 13502 // canceling dialog must scroll back to the caret position in the document // (in case the visible area of document has changed because of incremental search) JTextComponent c = Utilities.getLastActiveComponent(); if (c != null) c.getCaret().setDot(c.getCaret().getDot()); } } /** Panel that holds the find logic */ public class FindPanel extends FindDialogPanel implements ItemListener, KeyListener, ActionListener { Map findProps = Collections.synchronizedMap(new HashMap(20)); Map objToProps = Collections.synchronizedMap(new HashMap(20)); FindSupport findSupport = FindSupport.getFindSupport(); static final long serialVersionUID =917425125419841466L; FindPanel() { objToProps.put(findWhat, SettingsNames.FIND_WHAT); objToProps.put(replaceWith, SettingsNames.FIND_REPLACE_WITH); objToProps.put(highlightSearch, SettingsNames.FIND_HIGHLIGHT_SEARCH); objToProps.put(incSearch, SettingsNames.FIND_INC_SEARCH); objToProps.put(matchCase, SettingsNames.FIND_MATCH_CASE); objToProps.put(smartCase, SettingsNames.FIND_SMART_CASE); objToProps.put(wholeWords, SettingsNames.FIND_WHOLE_WORDS); objToProps.put(regExp, SettingsNames.FIND_REG_EXP); objToProps.put(bwdSearch, SettingsNames.FIND_BACKWARD_SEARCH); objToProps.put(wrapSearch, SettingsNames.FIND_WRAP_SEARCH); regExp.setEnabled(false); // !!! remove when regexp search is fine regExp.setVisible(false); load(); findWhat.getEditor().getEditorComponent().addKeyListener(this); findWhat.addActionListener(this); replaceWith.getEditor().getEditorComponent().addKeyListener(this); replaceWith.addActionListener(this); highlightSearch.addItemListener(this); incSearch.addItemListener(this); matchCase.addItemListener(this); smartCase.addItemListener(this); wholeWords.addItemListener(this); regExp.addItemListener(this); bwdSearch.addItemListener(this); wrapSearch.addItemListener(this); } protected Map getFindProps() { return findProps; } void putProperty(Object component, Object value) { String prop = (String)objToProps.get(component); if (prop != null) { findProps.put(prop, value); incSearchTimer.restart(); // findSupport.incSearch(findProps); } } Object getProperty(Object component) { String prop = (String)objToProps.get(component); return (prop != null) ? findProps.get(prop) : null; } boolean getBooleanProperty(Object component) { Object prop = getProperty(component); return (prop != null) ? ((Boolean)prop).booleanValue() : false; } private void changeVisibility(boolean v) { replaceWith.setVisible(v); replaceWithLabel.setVisible(v); } protected void updateFocus() { JTextComponent c = Utilities.getLastActiveComponent(); String selText = null; if (c != null) { selText = c.getSelectedText(); if (selText != null) { int n = selText.indexOf( '\n' ); if (n >= 0 ) selText = selText.substring(0, n); updateFindWhat(selText.trim()); changeFindWhat(); }else{ if (getProperty(findWhat)!=null){ findWhat.getEditor().setItem(getProperty(findWhat)); } } } final String selTextFinal = selText; SwingUtilities.invokeLater( new Runnable() { public void run() { updateFindWhat(selTextFinal); findWhat.getEditor().getEditorComponent().requestFocus(); findWhat.getEditor().selectAll(); } } ); } protected void updateFind() { changeVisibility(false); } protected void updateReplace() { changeVisibility(true); } private List getHistory( String identifier ) { List history = (List)EditorState.get( HISTORY_KEY_BASE + identifier + "-history" ); //NOI18N if( history == null ) { history = new ArrayList(); } return history; } private void putHistory( String identifier, List history ) { EditorState.put( HISTORY_KEY_BASE + identifier + "-history", history ); //NOI18N } private void updateHistory(JComboBox c, String identifier) { Object item = c.getEditor().getItem(); List history = getHistory( identifier); if( item != null ) { int index = history.indexOf( item ); if( index >= 0 ) { history.remove( index ); } history.add( 0, item ); } putHistory( identifier, history ); javax.swing.DefaultComboBoxModel m = new javax.swing.DefaultComboBoxModel( history.toArray() ); c.setModel(m); } protected void updateFindHistory() { updateHistory(findWhat, "find" ); // NOI18N } protected void updateReplaceHistory() { updateHistory(replaceWith, "replace" ); // NOI18N } protected void updateFindWhat(String selectedText) { findWhat.getEditor().setItem(selectedText); } private void revertMap(){ Object prop = findProps.get(FindSupport.REVERT_MAP); if (!(prop instanceof Map)) return; Map revertMap = (Map)prop; for( Iterator i = revertMap.keySet().iterator(); i.hasNext(); ) { String key = (String)i.next(); Object obj = findProps.get(key); boolean value = ( obj != null ) ? ((Boolean)obj).booleanValue() : false; value = !value; findProps.put(key, value ? Boolean.TRUE : Boolean.FALSE); } findProps.put(FindSupport.REVERT_MAP, null); } /** Load the current find properties from those in FindSupport */ void load() { findProps.putAll(findSupport.getFindProperties()); revertMap(); java.util.List history = getHistory( "find" ); // NOI18N javax.swing.DefaultComboBoxModel m = new javax.swing.DefaultComboBoxModel( history.toArray() ); findWhat.setModel(m); history = getHistory( "replace" ); // NOI18N m = new javax.swing.DefaultComboBoxModel( history.toArray() ); replaceWith.setModel(m); findWhat.getEditor().setItem(getProperty(findWhat)); replaceWith.getEditor().setItem(getProperty(replaceWith)); highlightSearch.setSelected(getBooleanProperty(highlightSearch)); incSearch.setSelected(getBooleanProperty(incSearch)); matchCase.setSelected(getBooleanProperty(matchCase)); smartCase.setSelected(getBooleanProperty(smartCase)); wholeWords.setSelected(getBooleanProperty(wholeWords)); regExp.setSelected(getBooleanProperty(regExp)); bwdSearch.setSelected(getBooleanProperty(bwdSearch)); wrapSearch.setSelected(getBooleanProperty(wrapSearch)); } /** Save the current find properties into those in FindSupport */ void save() { findSupport.putFindProperties(findProps); } void changeFindWhat() { Object old = getProperty(findWhat); Object cur = findWhat.getEditor().getItem(); if (old == null || !old.equals(cur)) { putProperty(findWhat, cur); } } void changeReplaceWith() { Object old = getProperty(replaceWith); Object cur = replaceWith.getEditor().getItem(); if (old == null || !old.equals(cur)) { putProperty(replaceWith, cur); } } private void postChangeCombos() { SwingUtilities.invokeLater( new Runnable() { public void run() { changeFindWhat(); changeReplaceWith(); if (regExp.isSelected()) { } } } ); } public void keyPressed(KeyEvent evt) { postChangeCombos(); } public void keyReleased(KeyEvent evt) { SwingUtilities.invokeLater( new Runnable() { public void run() { changeFindWhat(); changeReplaceWith(); } } ); } public void keyTyped(KeyEvent evt) { if (evt.getKeyChar() == '\n') { FindDialogSupport.this.actionPerformed( new ActionEvent(findButtons[0], 0, null)); } } public void itemStateChanged(ItemEvent evt) { Boolean val = (evt.getStateChange() == ItemEvent.SELECTED) ? Boolean.TRUE : Boolean.FALSE; putProperty(evt.getSource(), val); } public void actionPerformed(ActionEvent evt) { postChangeCombos(); } } }